﻿using System;
using System.ComponentModel;
using System.Reflection;
using Microsoft.WowAddonStudio.FrameXml.Serialization;

namespace Microsoft.WowAddonStudio.FrameXml.Components.Descriptors
{
    public class InheritableObjectPropertyDescriptor : FrameXmlPropertyDescriptor<InheritableObject>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="InheritableObjectPropertyDescriptor"/> class.
        /// </summary>
        /// <param name="instance">The inheritable object.</param>
        /// <param name="propertyDescriptor">The property descriptor.</param>
        /// <param name="propertyName">Name of the property.</param>
        public InheritableObjectPropertyDescriptor(InheritableObject instance, PropertyDescriptor propertyDescriptor, string propertyName)
            : base(instance, propertyDescriptor, propertyName)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="InheritableObjectPropertyDescriptor"/> class.
        /// </summary>
        /// <param name="inheritableObject">The inheritable object.</param>
        /// <param name="propertyDescriptor">The property descriptor.</param>
        public InheritableObjectPropertyDescriptor(InheritableObject inheritableObject, PropertyDescriptor propertyDescriptor)
            : base(inheritableObject, propertyDescriptor)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="InheritableObjectPropertyDescriptor"/> class.
        /// </summary>
        /// <param name="propertyDescriptor">The property descriptor.</param>
        public InheritableObjectPropertyDescriptor(PropertyDescriptor propertyDescriptor)
            : base(propertyDescriptor)
        {
        }

        /// <summary>
        /// When overridden in a derived class, sets the value of the component to a different value.
        /// </summary>
        /// <param name="component">The component with the property value that is to be set.</param>
        /// <param name="value">The new value.</param>
        public override void SetValue(object component, object value)
        {
            if (component is IPropertyValidator)
                ((IPropertyValidator) component).ValidatePoperty(PropertyDescriptor.Name, value);

            if (Instance != null)
            {
                if (!Instance.HasValue(PropertyName))
                {
                    var inheritedPropertyValue = Instance.GetInheritedValue<SerializationObject>(PropertyName);
                    if (inheritedPropertyValue is ICloneable)
                    {
                        // Clone the component and replace in component variable
                        object clonedPropertyValue = ((ICloneable) inheritedPropertyValue).Clone();

                        // Check whether the value was set on the inherited property value
                        if (inheritedPropertyValue == component)
                        {
                            // Set value on the cloned property value
                            PropertyDescriptor.SetValue(clonedPropertyValue, value);
                        }
                        else
                        {
                            // TODO: Generalize this to multiple levels and non-conforming names
                            // See if we can find the nested property
                            PropertyInfo nestedPropertyInfo = inheritedPropertyValue.GetType().GetProperty(PropertyDescriptor.ComponentType.Name);
                            if (nestedPropertyInfo != null)
                            {
                                // Get the value of the nested property on the cloned property
                                object clonedNestedPropertyValue = nestedPropertyInfo.GetValue(clonedPropertyValue, null);

                                // Set the value on the cloned nested property value
                                PropertyDescriptor.SetValue(clonedNestedPropertyValue, value);
                            }
                        }

                        // Set cloned property value
                        Instance.SetValue(PropertyName, clonedPropertyValue);
                    }
                }
                else
                {
                    // Set value on original component
                    base.SetValue(component, value);
                }
            }
            else
            {
                // Set value on original component
                base.SetValue(component, value);
            }
        }
    }
}
